package service

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path"
	"sort"
	"strings"

	"lucidity.note/app/common"
	"lucidity.note/app/common/bean"
	"lucidity.note/app/common/db"
	"lucidity.note/app/common/logger"
)

type NoteTreeService struct{}

// 获取笔记树服务
func (*NoteTreeService) GetNoteTreeService(notebookId string) ([]*bean.NoteTreeNode, error) {
	nbService := &NotebookService{}
	var err error
	var nodes []*bean.NoteTreeNode
	var notebook *bean.Notebook

	notebook, err = nbService.GetNotebookByIdService(notebookId)
	if nil != err {
		err = fmt.Errorf(fmt.Sprintf("加载笔记树取笔记本信息失败：%s", err.Error()))
		logger.FAULT.Println(err)
		return nil, err
	}

	if common.IsBlank(notebook.Path) {
		err = fmt.Errorf(fmt.Sprintf("加载笔记树取笔记本信息失败：%s笔记本没有配置笔记仓储路径", notebook.Name))
		logger.FAULT.Println(err)
		return nil, err
	}

	localPath := fmt.Sprintf("%s/%s", notebook.Path, notebook.Name)
	if nodes, err = directoryTraversal(notebookId, localPath, "", 0); nil != err {
		err = fmt.Errorf(fmt.Sprintf("加载笔记树取笔记本信息失败：递归节点遍历=> %s", err.Error()))
		logger.FAULT.Println(err)
		return nil, err
	}

	bys, err := json.Marshal(nodes)
	if nil != err {
		err = fmt.Errorf(fmt.Sprintf("加载笔记树取笔记本信息失败：json序列化=> %s", err.Error()))
		logger.FAULT.Println(err)
		return nil, err
	}
	// 将笔记树存储到内存模型 NustDB 中
	memory := db.GetMemoryNustDB()
	memory.Insert(bean.GetNustDBBucketOfNoteTree(), notebookId, string(bys))
	return nodes, err
}

// rename 服务
func (*NoteTreeService) RenameTreeNodeService(notebookId string, nodeId string, newName string) (*bean.NoteTreeNode, error) {
	var err error
	var nodes []*bean.NoteTreeNode
	var findNode *bean.NoteTreeNode

	// NustDB 中取数
	memory := db.GetMemoryNustDB()
	jsonStr, e := memory.Select(bean.GetNustDBBucketOfNoteTree(), notebookId)
	if nil != e {
		err = fmt.Errorf(fmt.Sprintf("重命名失败：无法获取到 NotebookId={%s}的内存数据", notebookId))
		logger.FAULT.Println(err)
		return nil, err
	}

	nodes = make([]*bean.NoteTreeNode, 0)
	if e := json.Unmarshal([]byte(jsonStr), &nodes); nil != e {
		err = fmt.Errorf(fmt.Sprintf("重命名失败：序列化 json 数据失败=> %s", e.Error()))
		logger.FAULT.Println(err)
		return nil, err
	}

	// 找到对应的节点
	findNode, _ = searchNode(nodes, nodeId)
	if nil == findNode {
		err = fmt.Errorf(fmt.Sprintf("重命名失败：无法查找到ID={%s}的节点数据", nodeId))
		logger.FAULT.Println(err)
		return nil, err
	}

	if findNode.IsDir && common.IsBlank(newName) {
		err = fmt.Errorf("重命名名称不能为空")
		return nil, err
	}

	// 当输入的新文件名不包含后缀的时候,自动补充后缀
	if !findNode.IsDir && common.IsBlank(path.Ext(newName)) {
		newName = fmt.Sprintf("%s.%s", newName, findNode.Suffix)
	}

	// 磁盘数据更新
	oldPath, newPath, e := rename(findNode, newName)
	if nil != e {
		err = fmt.Errorf(fmt.Sprintf("重命名失败：更新本地文件名称或目录异常=>%s", e.Error()))
		logger.FAULT.Println(err)
		return nil, err
	}
	// 节点数据更新
	findNode.Name = newName
	findNode.Path = newPath

	// 如果是文件夹重命名,那么所有的文件夹下的子文件名称的路径都需要修改
	updateSonNodesPath(findNode, newPath, oldPath)

	// 重新将数据刷入到 NustDB 内存模型存储中
	bys, e := json.Marshal(nodes)
	if nil != e {
		err = fmt.Errorf(fmt.Sprintf("重命名失败：json序列化=> %s", e.Error()))
		// fixme: 此处失败要回滚文件
		logger.FAULT.Println(err)
		return nil, err
	}
	// 将笔记树存储到内存模型 NustDB 中
	memory.Insert(bean.GetNustDBBucketOfNoteTree(), notebookId, string(bys))
	// fixme: insert 失败之后进行失败回滚

	return findNode, err
}

// 删除节点服务
func (*NoteTreeService) DeleteTreeNodeService(notebookId, nodeId string) ([]*bean.NoteTreeNode, error) {
	var err error
	var nodes []*bean.NoteTreeNode
	// 加载笔记本信息
	memory := db.GetMemoryNustDB()
	jsonStr, e := memory.Select(bean.GetNustDBBucketOfNoteTree(), notebookId)
	if nil != e {
		err = fmt.Errorf(fmt.Sprintf("删除节点失败：无法获取到 NotebookId={%s}的内存数据", notebookId))
		return nil, err
	}

	nodes = make([]*bean.NoteTreeNode, 0)
	if e := json.Unmarshal([]byte(jsonStr), &nodes); nil != e {
		err = fmt.Errorf(fmt.Sprintf("删除节点失败：序列化 json 数据失败=> %s", e.Error()))
		return nil, err
	}

	delNode, parentNode := searchNode(nodes, nodeId)
	// 删除本地文件
	if e := os.RemoveAll(delNode.Path); nil != e {
		err = fmt.Errorf("删除节点失败：删除路径{%s}失败=> %s", delNode.Path, e.Error())
		return nil, err
	}

	if nil != parentNode && nil != parentNode.Childrens && len(parentNode.Childrens) > 0 {
		// 删除缓存中的当前节点，并且在删除之后将新的节点数据返回
		parentNode.Childrens = deleteNode(delNode, parentNode.Childrens)
	} else {
		// 如果没有上级目录，说明当前节点处理顶层节点
		nodes = deleteNode(delNode, nodes)
	}

	// 处理完毕之后重新加入缓存
	if bys, e := json.Marshal(nodes); nil != e {
		err = fmt.Errorf(fmt.Sprintf("删除节点失败：json序列化=> %s", e.Error()))
		return nil, err
	} else {
		memory.Insert(bean.GetNustDBBucketOfNoteTree(), notebookId, string(bys))
	}

	return nodes, err
}

// 新增节点
func (*NoteTreeService) NewTreeNode(notebookId, parentId, name, types string) ([]*bean.NoteTreeNode, *bean.NoteTreeNode, error) {
	var err error
	var nodes []*bean.NoteTreeNode
	var newNode *bean.NoteTreeNode

	memory := db.GetMemoryNustDB()
	jsonStr, e := memory.Select(bean.GetNustDBBucketOfNoteTree(), notebookId)
	if nil != e {
		err = fmt.Errorf(fmt.Sprintf("新增节点失败：无法获取到 NotebookId={%s}的内存数据", notebookId))
		return nodes, nil, err
	}

	nodes = make([]*bean.NoteTreeNode, 0)
	if e := json.Unmarshal([]byte(jsonStr), &nodes); nil != e {
		err = fmt.Errorf(fmt.Sprintf("新增节点失败：序列化 json 数据失败=> %s", e.Error()))
		return nodes, nil, err
	}

	parentNode, _ := searchNode(nodes, parentId)
	// 错误校验，在非目录节点下不允许新增节点
	if !parentNode.IsDir {
		err := fmt.Errorf("新增节点失败：只有目录节点才允许新增子节点")
		return nodes, nil, err
	}

	// 组装节点
	var suffix string = ""
	var isDir bool = false
	if strings.EqualFold(types, "folder") {
		isDir = true
	} else {
		suffix = path.Ext(name)[1:]

		if common.IsBlank(suffix) {
			err = fmt.Errorf("请输入文件后缀")
			return nil, nil, err
		}

		noteTextConf := FindNoteTextBySuffix(suffix)
		if nil == noteTextConf {
			err = fmt.Errorf(fmt.Sprintf("当前[%s]后缀格式暂不支持.", suffix))
			return nil, nil, err
		} else if !noteTextConf.Enable {
			err = fmt.Errorf(fmt.Sprintf("当前 [%s] 后缀格式暂没开启，请前往用户设置将 [%s] 文本启用.", suffix, noteTextConf.Name))
			return nil, nil, err
		}
	}

	newNode = &bean.NoteTreeNode{
		NotebookId: notebookId,
		Id:         common.RandomWord(10, 62),
		Name:       name,
		Level:      parentNode.Level + 1,
		ParentId:   parentId,
		IsDir:      isDir,
		Path:       clearPath(fmt.Sprintf("%s/%s", parentNode.Path, name)),
		Suffix:     suffix,
		Editable:   false,
		Childrens:  nil,
	}

	// 本地文件更新
	if isDir {
		if e := os.Mkdir(newNode.Path, os.ModePerm); nil != e {
			err := fmt.Errorf(fmt.Sprintf("新增节点失败：创建本地节点失败=> %s", e.Error()))
			return nil, nil, err
		}
	} else {
		if file, e := os.Create(newNode.Path); nil != e {
			err := fmt.Errorf(fmt.Sprintf("新增节点失败：创建本地文件节点失败=> %s", e.Error()))
			return nil, nil, err
		} else {
			defer file.Close()
		}
	}

	// 如果父节点的子节点切片为 nil，则初始化一个切片存储新的子节点
	if nil == parentNode.Childrens {
		parentNode.Childrens = make([]*bean.NoteTreeNode, 0)
	}
	parentNode.Childrens = append(parentNode.Childrens, newNode)

	// 处理完毕之后重新加入缓存
	if bys, e := json.Marshal(nodes); nil != e {
		err = fmt.Errorf(fmt.Sprintf("新增节点失败：json序列化=> %s", e.Error()))
	} else {
		memory.Insert(bean.GetNustDBBucketOfNoteTree(), notebookId, string(bys))
	}

	return nodes, newNode, err
}

// 获取文件内容
func (*NoteTreeService) GetNodeFileContentService(notebookId, nodeId string) (*bean.NoteNodeRespParam, error) {
	var responseParam *bean.NoteNodeRespParam
	var err error
	memory := db.GetMemoryNustDB()

	jsonStr, e := memory.Select(bean.GetNustDBBucketOfNoteTree(), notebookId)
	if nil != e {
		err = fmt.Errorf(fmt.Sprintf("读取文件内容失败：无法获取到 NotebookId={%s}的内存数据=> %s", notebookId, e.Error()))
		return nil, err
	}

	nodes := make([]*bean.NoteTreeNode, 0)
	if e := json.Unmarshal([]byte(jsonStr), &nodes); nil != e {
		err = fmt.Errorf(fmt.Sprintf("读取文件内容失败：序列化 json 数据失败=> %s", e.Error()))
		return nil, err
	}

	node, _ := searchNode(nodes, nodeId)
	if bys, e := os.ReadFile(node.Path); nil != e {
		err = fmt.Errorf(fmt.Sprintf("读取文件内容失败：读取文件内容失败=> %s", e.Error()))
		return nil, err
	} else {
		content := string(bys)
		appearance := GetAppearanceConfig()

		metaInfo := GetCodeMirrorMetaInfoBySuffix(node.Suffix)
		if nil == metaInfo {
			metaInfo = GetCodeMirrorMetaInfoBySuffix("txt")
		}

		isShowPrewviewBox, editboxWidth, previewWidth := isShowPreviewBox(metaInfo.Mode)
		responseParam = &bean.NoteNodeRespParam{
			Suffix:        node.Suffix,
			Name:          node.Name,
			Theme:         appearance.EditorTheme,
			Mode:          metaInfo.Mode,
			Mime:          metaInfo.Mime,
			Content:       content,
			Path:          node.Path,
			Dependencies:  metaInfo.Dependencies,
			Addons:        metaInfo.Addons,
			IsShowPreview: isShowPrewviewBox,
			EditboxWidth:  editboxWidth,
			PreviewWidth:  previewWidth,
		}
	}

	return responseParam, nil
}

// 新增根节点
func (*NoteTreeService) NewRootTreeNodeService(notebookId, name, nodeType string) (nodes []*bean.NoteTreeNode, node *bean.NoteTreeNode, err error) {
	memory := db.GetMemoryNustDB()
	nbService := &NotebookService{}
	notebook, err := nbService.GetNotebookByIdService(notebookId)

	if nil != err {
		err = fmt.Errorf(fmt.Sprintf("新增根节点失败：加载笔记树取笔记本信息失败=> %s", err.Error()))
		logger.FAULT.Println(err)
		return nil, nil, err
	}

	jsonStr, e := memory.Select(bean.GetNustDBBucketOfNoteTree(), notebookId)
	if nil != e {
		err = fmt.Errorf(fmt.Sprintf("新增根节点失败：无法获取到 NotebookId={%s}的内存数据", notebookId))
		return nil, nil, err
	}

	nodes = make([]*bean.NoteTreeNode, 0)
	if e := json.Unmarshal([]byte(jsonStr), &nodes); nil != e {
		err = fmt.Errorf(fmt.Sprintf("新增根节点失败：序列化 json 数据失败=> %s", e.Error()))
		return nil, nil, err
	}

	isDir := true
	suffix := ""
	if strings.EqualFold("file", nodeType) {
		isDir = false
		suffix = path.Ext(name)[1:]

		if common.IsBlank(suffix) {
			err = fmt.Errorf("请输入文件后缀")
			return nil, nil, err
		}

		noteTextConf := FindNoteTextBySuffix(suffix)
		if nil == noteTextConf {
			err = fmt.Errorf(fmt.Sprintf("当前[%s]后缀格式暂不支持.", suffix))
			return nil, nil, err
		} else if !noteTextConf.Enable {
			err = fmt.Errorf(fmt.Sprintf("当前 [%s] 后缀格式暂没开启，请前往用户设置将 [%s] 文本启用.", suffix, noteTextConf.Name))
			return nil, nil, err
		}
	}

	newNode := &bean.NoteTreeNode{
		NotebookId: notebookId,
		Id:         common.RandomWord(10, 62),
		Name:       name,
		Level:      0,
		ParentId:   "",
		IsDir:      isDir,
		Path:       clearPath(fmt.Sprintf("%s/%s/%s", notebook.Path, notebook.Name, name)),
		Suffix:     suffix,
		Editable:   false,
		Childrens:  nil,
	}
	nodes = append(nodes, newNode)

	// 创建磁盘文件or目录
	if isDir {
		if e := os.Mkdir(newNode.Path, os.ModePerm); nil != e {
			err := fmt.Errorf(fmt.Sprintf("新增根节点失败：创建本地目录节点失败=> %s", e.Error()))
			return nil, nil, err
		}
	} else {
		if file, e := os.Create(newNode.Path); nil != e {
			err := fmt.Errorf(fmt.Sprintf("新增根节点失败：创建本地文件节点失败=> %s", e.Error()))
			return nil, nil, err
		} else {
			defer file.Close()
		}
	}

	// 存入缓存
	if bys, e := json.Marshal(nodes); nil != e {
		err = fmt.Errorf(fmt.Sprintf("新增根节点失败：json序列化=> %s", e.Error()))
		return nil, nil, err
	} else {
		memory.Insert(bean.GetNustDBBucketOfNoteTree(), notebookId, string(bys))
	}

	return nodes, newNode, nil
}

// 更新文件内容
func (*NoteTreeService) UpdateFileContentService(updataParam *bean.UpdateNodeContent) error {
	file, err := os.OpenFile(updataParam.Path, os.O_CREATE|os.O_RDWR, 0666)
	if err != nil {
		logger.FAULT.Printf("更新文件内容: 打开文件[%s]失败 => {%s}", updataParam.Path, err.Error())
		return fmt.Errorf("更新文件内容: 打开文件[%s]失败", updataParam.Path)
	}
	defer file.Close()

	wirter := bufio.NewWriter(file)
	if _, err := wirter.WriteString(updataParam.Content); nil != err {
		logger.FAULT.Printf("更新文件内容: 写入[%s]文件内容失败 => {%s}", updataParam.Path, err.Error())
		return errors.New("文件写入失败")
	}

	if err := wirter.Flush(); err != nil {
		logger.FAULT.Printf("更新文件内容: 文件内容刷入缓存失效 => {%s}", err.Error())
		return errors.New("文件写入失败")
	}
	return nil
}

// 是否展示预览框
func isShowPreviewBox(mode string) (bool, int8, int8) {
	isShowPreviewBox := strings.EqualFold("markdown", mode)
	if isShowPreviewBox {
		return isShowPreviewBox, 12, 12
	}
	return isShowPreviewBox, 24, 0
}

// 删除节点
func deleteNode(delNode *bean.NoteTreeNode, nodes []*bean.NoteTreeNode) []*bean.NoteTreeNode {
	index := 0
	for _, item := range nodes {
		if !strings.EqualFold(item.Id, delNode.Id) {
			nodes[index] = item
			index++
		}
	}
	nodes = nodes[0:index]
	return nodes
}

// 查找节点
func searchNode(nodes []*bean.NoteTreeNode, nodeId string) (findNode *bean.NoteTreeNode, parentNode *bean.NoteTreeNode) {
	if nil == nodes || len(nodes) == 0 {
		return nil, nil
	}

	var node *bean.NoteTreeNode
	var parent *bean.NoteTreeNode
	for _, item := range nodes {
		if strings.EqualFold(item.Id, nodeId) {
			node = item
			parent = nil
			break
		}

		if item.IsDir {
			node, parent = searchNode(item.Childrens, nodeId)

			if nil == parent {
				parent = item
			}

			if nil != node {
				break
			}
		}
	}

	return node, parent
}

// 更新子节点的路径信息
func updateSonNodesPath(parentNode *bean.NoteTreeNode, newPath, oldPath string) {
	if nil == parentNode {
		return
	}
	sonNodes := parentNode.Childrens
	for _, node := range sonNodes {
		node.Path = strings.ReplaceAll(node.Path, oldPath, newPath)
		if len(node.Childrens) > 0 {
			updateSonNodesPath(node, newPath, oldPath)
		}
	}
}

// 目录遍历
func directoryTraversal(notebookId string, dirPath string, parent string, level int8) ([]*bean.NoteTreeNode, error) {
	var treeNodes []*bean.NoteTreeNode = make([]*bean.NoteTreeNode, 0)
	var err error
	if items, e := os.ReadDir(dirPath); nil != e {
		err = fmt.Errorf(fmt.Sprintf("{%s}目录遍历失败：%s", dirPath, e.Error()))
		return nil, err
	} else {
		for _, item := range items {
			// 当前节点路径
			currPath := fmt.Sprintf("%s/%s", dirPath, item.Name())
			// 是否为目录节点
			isDir := item.IsDir()

			// 如果目录在忽略扫描里面直接排除
			if common.Contains(GetAppInternalConf().LucidityNote.Ignores, item.Name()) {
				continue
			}

			suffix := ""
			if !isDir {
				suffix = path.Ext(item.Name())
				if common.IsBlank(suffix) { // 不包含后缀的移除不展示
					continue
				}
				suffix = suffix[1:]
			}

			// 创建节点
			node := &bean.NoteTreeNode{
				NotebookId: notebookId,
				Id:         common.RandomWord(10, 62),
				Name:       item.Name(),
				Level:      level,
				ParentId:   parent,
				IsDir:      isDir,
				Path:       clearPath(currPath),
				Suffix:     suffix,
				Editable:   false,
				Childrens:  nil,
			}

			if isDir {
				if childs, e := directoryTraversal(notebookId, currPath, node.Id, level+1); nil != e {
					err = fmt.Errorf(fmt.Sprintf("递归目录失败：%s => %s", e.Error(), dirPath))
					return nil, err
				} else {
					node.Childrens = childs
					treeNodes = append(treeNodes, node)
				}
			} else {
				treeNodes = append(treeNodes, node)
			}
		}
	}

	// 节点排序: 将文件夹和文件分开排序
	sort.Slice(treeNodes, func(i, j int) bool {
		return treeNodes[i].IsDir != treeNodes[j].IsDir
	})

	return treeNodes, err
}

// 节点数据重命名
func rename(node *bean.NoteTreeNode, newName string) (string, string, error) {
	// old path
	oldPath := node.Path
	// new path
	newPath := fmt.Sprintf("%s/%s", path.Dir(node.Path), newName)
	err := os.Rename(oldPath, newPath)
	return oldPath, newPath, err
}

func clearPath(path string) string {
	return strings.ReplaceAll(path, "//", "/")
}
